package com.slipper.system.bpmn.service;

import java.util.*;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;

import com.slipper.codeless.service.CodelessService;
import com.slipper.constants.Constants;
import com.slipper.constants.FlowConstants;
import com.slipper.core.transaction.ReadTransactional;
import com.slipper.core.transaction.WriteTransactional;
import com.slipper.bpmn.BpmnEngine;
import com.slipper.bpmn.vo.BpmnVO;
import com.slipper.system.flow.bo.*;
import com.slipper.system.flow.dao.*;
import com.slipper.system.sequence.service.SequenceService;
import com.slipper.util.ResultUtil;
import com.slipper.util.Tool;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.slipper.system.bpmn.bo.BpmnBO;
import com.slipper.system.employee.bo.EmployeeBO;
import com.slipper.system.organization.bo.OrganizationBO;
import com.slipper.bpmn.dealer.INodeDealer;
import com.slipper.bpmn.dealer.NodeDealerFactory;
import com.slipper.system.employee.dao.EmployeeDao;
import com.slipper.system.organization.dao.OrganizationDao;

@Service
@ReadTransactional
public class BpmnService {
    private final Logger logger = LoggerFactory.getLogger(BpmnService.class);

    @Resource
    private FlowNodeDao flowNodeDao;
    @Resource
    private FlowLineDao flowLineDao;
    @Resource
    private FlowDao flowDao;
    @Resource
    private FlowInstNodeDao flowInstNodeDao;
    @Resource
    private FlowInstDao flowInstDao;
    @Resource
    private FlowPendingDao flowPendingDao;
    @Resource
    private OrganizationDao organizationDao;
    @Resource
    private EmployeeDao employeeDao;
    @Resource
    private SequenceService sequenceService;
    @Resource
    private CodelessService codelessService;
    @Resource
    private BpmnEngine bpmnEngine;

    public BpmnBO queryBy(BpmnBO bo) {
        // 根据实例节点ID加载实例节点数据
        if (!Tool.isBlank(bo.getInstNodeId())) {
            bo.setPendingInstNodeId(bo.getInstNodeId());

            FlowInstNodeBO flowInstNode = flowInstNodeDao.queryBy(bo.getInstNodeId());
            bo.setInstId(flowInstNode.getInstId());
            if (FlowConstants.FlowInstNode.DEAL_STATUS_PENDING.equals(flowInstNode.getDealStatus())) {
                FlowPendingBO pending = flowPendingDao.queryByInstNodeId(flowInstNode.getId());
                if (pending.getCurrManId().equals(bo.getCurrEmpId())) {
                    bo.setNodeId(flowInstNode.getCurrFlowNodeId());
                    bo.setInstId(flowInstNode.getInstId());
                    flowPendingDao.updateRead(bo.getInstNodeId(), System.currentTimeMillis());
                } else {
                    bo.setPendingInstNodeId(null);
                }
            } else {
                bo.setInstNodeId(null);
            }
        }

        // 根据流程实例ID加载流程实例数据
        if (!Tool.isBlank(bo.getInstId())) {
            bo.setStartFlow(false);
            bo.setFlowInst(flowInstDao.queryBy(bo.getInstId()));
            bo.setFlowId(bo.getFlowInst().getFlowId());

            // 加载所有经过的实例节点
            FlowInstNodeBO flowInstNodeQO = new FlowInstNodeBO();
            flowInstNodeQO.setInstId(bo.getInstId());
            bo.setFlowInstNodeList(flowInstNodeDao.queryList(flowInstNodeQO));

            if (!Tool.isBlank(bo.getInstNodeId())) {
                for (FlowInstNodeBO flowInstNode : bo.getFlowInstNodeList()) {
                    if (bo.getInstNodeId().equals(flowInstNode.getId())) {
                        bo.setFlowInstNode(flowInstNode);
                        break;
                    }
                }
            }
        }

        if (bo.getFlowInstNode() != null && Tool.isBlank(bo.getFlowInstNode().getDataId())) {
            FlowInstNodeBO preFlowInstNode = bpmnEngine.findFlowInstNode(
                    bo.getFlowInstNode().getCurrFlowNodeId(),
                    bo.getFlowInstNode().getPreInstNodeId(),
                    bo.getFlowInstNode().getDealManId(),
                    0,
                    bo.getFlowInstNodeList());
            if (preFlowInstNode != null && Tool.isNotBlank(preFlowInstNode.getDataId())) {
                bo.getFlowInstNode().setDataId(preFlowInstNode.getDataId());
                bo.getFlowInstNode().setCopyData(true);
            }
        }

        // 根据流程ID加载流程数据
        if (!Tool.isBlank(bo.getFlowId())) {
            bo.setFlow(flowDao.queryBy(bo.getFlowId()));
        }

        // 加载所有节点
        FlowNodeBO flowNodeQO = new FlowNodeBO();
        flowNodeQO.setFlowId(bo.getFlowId());
        List<FlowNodeBO> flowNodeList = flowNodeDao.queryList(flowNodeQO);
        flowNodeList.sort(Comparator.comparing(FlowNodeBO::getOrdinal, Comparator.nullsLast(Integer::compareTo)));
        bo.setFlowNodeList(flowNodeList);

        // 查询所有节点线路
        FlowLineBO flowLineQO = new FlowLineBO();
        flowLineQO.setFlowId(bo.getFlowId());
        List<FlowLineBO> flowLineList = flowLineDao.queryList(flowLineQO);
        bo.setFlowLineList(flowLineList);

        // 根据当前节点ID加载当前节点数据
        if (Tool.isBlank(bo.getNodeId()) && bo.isStartFlow()) {
            FlowNodeBO firstNode = bpmnEngine.findFirstTaskNode(flowNodeList, flowLineList);
            if (firstNode == null) {
                firstNode = new FlowNodeBO();
            }
            bo.setFlowNode(firstNode);
        } else {
            bo.setFlowNode(flowNodeList.stream()
                    .filter(flowNode -> flowNode.getId().equals(bo.getNodeId()))
                    .findFirst()
                    .orElse(null));
        }

        bpmnEngine.checkOperator(bo);
        return bo;
    }

    @WriteTransactional
    public String save(BpmnVO vo) {
        String nodeId = vo.getFlowData().getNodeId();
        String instNodeId = vo.getFlowData().getInstNodeId();

        if (Tool.isBlank(instNodeId) && Tool.isBlank(nodeId)) {
            return ResultUtil.failureResult("流程节点编号/流程节点实例编号不正确！");
        }

        // 是否是开始节点，如果是开始节点，需要先给中台发待办消息，再发送更新待办消息
        if (Tool.isBlank(instNodeId)) {
            // 没有实例ID，证明此流程未开始
            FlowNodeBO flowNode = this.flowNodeDao.queryBy(nodeId);
            if (flowNode == null) {
                return ResultUtil.failureResult("流程节点编号不存在！");
            }

            FlowBO flow = this.flowDao.queryBy(flowNode.getFlowId());
            if (flow == null) {
                return ResultUtil.failureResult("流程编号不存在！");
            }

            List<FlowNodeBO> nodeList = this.flowNodeDao.queryNode(flow.getId(), FlowConstants.FlowNode.TYPE_START);
            if (nodeList.isEmpty()) {
                return ResultUtil.failureResult("开始节点不存在！");
            }
            FlowNodeBO startNode = nodeList.getFirst();

            List<FlowLineBO> lineList = this.flowLineDao.queryLine(flow.getId(), startNode.getId(), nodeId);
            if (lineList.isEmpty()) {
                return ResultUtil.failureResult("开始节点线路不存在！");
            }
            FlowLineBO startLine = lineList.getFirst();

            if (Tool.isBlank(flowNode.getModelId())) throw new RuntimeException("节点模型未配置！");
            vo.getFlowData().setFlowId(flow.getId());

            Map<String, Map<String, Object>> dataMap = codelessService.parsePageData(flowNode.getEditPageId(), vo.getPageData());
            Map<String, Object> modelData = dataMap.get(flowNode.getModelId());
            String title = modelData != null ? (String) modelData.get(flowNode.getTitleFieldName()) : null;

            // 流程未开始，生成流程实例数据
            FlowInstBO flowInst = new FlowInstBO();
            flowInst.setId(Tool.isBlank(vo.getFlowData().getInstId()) ? Tool.getUUID() : vo.getFlowData().getInstId());
            flowInst.setParentId(vo.getFlowData().getParentInstId());
            flowInst.setFlowId(flow.getId());
            flowInst.setTitle(title);
            flowInst.setCurrNodeNames(flowNode.getName());
            flowInst.setStartTime(System.currentTimeMillis());
            flowInst.setDealStatus(FlowConstants.FlowInst.DEAL_STATUS_UNDERWAY);
            flowInst.setUpdateTime(System.currentTimeMillis());
            flowInst.setCreateTime(System.currentTimeMillis());

            // 生成流水号
            if (!Tool.isBlank(flow.getSequenceId())) {
                flowInst.setCode(sequenceService.getNextSequenceById(flow.getSequenceId()));
            }

            flowInstDao.insert(flowInst);
            relationFlow(vo.getFlowData().getParentInstNodeId(), flowInst.getId());

            try {
                // 实例化开始节点
                INodeDealer startDealer = NodeDealerFactory.getNodeDealer(startNode.getType());
                FlowInstNodeBO startInstNode = startDealer.doDeal(-1, true, startNode, flowInst, null, null, vo);

                // 实例化当前节点
                INodeDealer dealer = NodeDealerFactory.getNodeDealer(flowNode.getType());
                flowNode.setInLineId(startLine.getId());
                FlowInstNodeBO flowInstNode = dealer.doDeal(-1, false, flowNode, flowInst, startInstNode, null, vo);

                instNodeId = flowInstNode.getId();
                vo.getFlowData().setInstNodeId(instNodeId);
            } catch (Exception e) {
                logger.error("节点数据生成出错！", e);
                throw new RuntimeException(e);
            }
        }

        if (instNodeId == null) {
            return ResultUtil.failureResult("流程参数配置出错，无法初始化流程！");
        }

        // 保存节点数据
        FlowInstNodeBO flowInstNode = this.flowInstNodeDao.queryBy(instNodeId);
        FlowNodeBO flowNode = this.flowNodeDao.queryBy(flowInstNode.getCurrFlowNodeId());
        FlowInstBO flowInst = this.flowInstDao.queryBy(flowInstNode.getInstId());
        vo.getFlowData().setFlowId(flowNode.getFlowId());
        vo.getFlowData().setNodeId(flowNode.getId());
        vo.getFlowData().setInstId(flowInst.getId());

        Map<String, Map<String, Object>> dataMap = codelessService.saveByPageId(
                Constants.getCurrentUser(),
                flowNode.getEditPageId(),
                null,
                vo.getPageData(),
                null);
        Map<String, Object> modelData = dataMap.get(flowNode.getModelId());
        flowInstNode.setDataId(modelData.get("id").toString());
        flowInstNodeDao.update(flowInstNode);

        if (Tool.isNotBlank(flowNode.getTitleFieldName())) {
            String title = (String) modelData.get(flowNode.getTitleFieldName());
            flowInst.setTitle(title);
        }

        if (vo.isPauseSave()) { // 暂存不处理后续流转
            this.updateCurrMan(flowInst.getId());
            return ResultUtil.successResult("暂存成功！", flowInstNode);
        }

        try {
            // 处理当前节点并流转到下一节点
            INodeDealer dealer = NodeDealerFactory.getNodeDealer(flowNode.getType());
            dealer.doDeal(0, true, flowNode, flowInst, flowInstNode, flowInstNode, vo);
            this.updateCurrMan(flowInst.getId());
        } catch (Exception e) {
            logger.error("节点流转出错！", e);
            throw new RuntimeException(e.getMessage());
        }
        return ResultUtil.successResult("处理成功！", flowInstNode);
    }

    @WriteTransactional
    public void relationFlow(String instNodeId, String subInstId) {
        if (Tool.isBlank(instNodeId) || Tool.isBlank(subInstId)) return;

        FlowInstNodeBO flowInstNode = this.flowInstNodeDao.queryBy(instNodeId);
        if (flowInstNode == null) return;

        flowInstNode.setSubInstId(subInstId);
        flowInstNodeDao.update(flowInstNode);
    }

    @WriteTransactional
    public String saveRetrieve(BpmnVO vo) {
        if (Tool.isBlank(vo.getFlowData().getInstNodeId())) return ResultUtil.failureResult("参数不正确！");

        FlowInstNodeBO flowInstNode = this.flowInstNodeDao.queryBy(vo.getFlowData().getInstNodeId());
        if (flowInstNode == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        FlowInstBO flowInst = this.flowInstDao.queryBy(flowInstNode.getInstId());
        if (!FlowConstants.FlowInst.DEAL_STATUS_UNDERWAY.equals(flowInst.getDealStatus())) {
            return ResultUtil.failureResult("流程为非待办中，不允许取回！");
        }

        // 删除后续实例节点及相应的数据
        deleteNextData(flowInstNode.getId());

        // 将待办设置为待办中
        FlowPendingBO pendingBO = flowPendingDao.queryByInstNodeId(flowInstNode.getId());
        if (pendingBO != null) {
            pendingBO.setStatus(FlowConstants.FlowPending.STATUS_PENDING);
            flowPendingDao.update(pendingBO);
        }

        // 将实例节点设置为待办中
        flowInstNode.setDealStatus(FlowConstants.FlowInstNode.DEAL_STATUS_PENDING);
        flowInstNodeDao.update(flowInstNode);

        updateCurrMan(flowInst.getId());
        return ResultUtil.successResult("取回成功！", flowInstNode);
    }

    public void deleteNextData(String instNodeId) {
        if (Tool.isBlank(instNodeId)) return;

        FlowInstNodeBO flowInstNodeQO = new FlowInstNodeBO();
        flowInstNodeQO.setPreInstNodeId(instNodeId);
        List<FlowInstNodeBO> flowInstNodeList = flowInstNodeDao.queryList(flowInstNodeQO);
        for (FlowInstNodeBO flowInstNode : flowInstNodeList) {
            // 删除实例节点数据
            if (Tool.isNotBlank(flowInstNode.getCurrFlowNodeId()) && Tool.isNotBlank(flowInstNode.getDataId())) {
                FlowNodeBO flowNode = this.flowNodeDao.queryBy(flowInstNode.getCurrFlowNodeId());
                if (flowNode != null && Tool.isNotBlank(flowNode.getModelId())) {
                    codelessService.deleteByModelId(flowNode.getModelId(), flowInstNode.getDataId());
                }
            }

            // 删除实例节点
            flowInstNodeDao.delete(flowInstNode);

            // 删除待办信息
            FlowPendingBO pendingBO = flowPendingDao.queryByInstNodeId(flowInstNode.getId());
            if (pendingBO != null) {
                flowPendingDao.delete(pendingBO);
            }

            // 删除下一实例节点
            deleteNextData(flowInstNode.getId());
        }
    }

    @WriteTransactional
    public String saveCompleted(BpmnVO vo) {
        if (vo.getFlowData().getInstNodeId() == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        FlowInstNodeBO flowInstNode = flowInstNodeDao.queryBy(vo.getFlowData().getInstNodeId());
        if (flowInstNode == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        FlowInstBO flowInstBO = this.flowInstDao.queryBy(flowInstNode.getInstId());
        if (flowInstBO == null) {
            return ResultUtil.failureResult("参数不正确！");
        }
        if (!FlowConstants.FlowInst.DEAL_STATUS_UNDERWAY.equals(flowInstBO.getDealStatus())) {
            return ResultUtil.failureResult("流程为非待办中，不允许办结！");
        }

        // 查出所有实例节点
        FlowInstNodeBO qo = new FlowInstNodeBO();
        qo.setInstId(flowInstNode.getInstId());
        qo.setDealStatus(FlowConstants.FlowInstNode.DEAL_STATUS_PENDING);
        List<FlowInstNodeBO> instNodeList = flowInstNodeDao.queryList(qo);

        // 节点办结
        instNodeList.forEach(instNode -> {
            instNode.setDealStatus(FlowConstants.FlowInstNode.DEAL_STATUS_FINISHED);
            instNode.setEndTime(System.currentTimeMillis());
            flowInstNodeDao.update(instNode);
        });

        // 待办办结
        flowPendingDao.queryListBy(flowInstBO.getId(), FlowConstants.FlowPending.STATUS_PENDING).forEach(pendingBO -> {
            pendingBO.setStatus(FlowConstants.FlowPending.STATUS_FINISHED);
            pendingBO.setFinishedTime(System.currentTimeMillis());
            flowPendingDao.update(pendingBO);
        });

        // 流程办结
        flowInstBO.setDealStatus(FlowConstants.FlowInst.DEAL_STATUS_COMPLETED);
        flowInstBO.setEndTime(System.currentTimeMillis());
        flowInstDao.update(flowInstBO);

        updateCurrMan(flowInstBO.getId());
        return ResultUtil.successResult("流程办结成功！", flowInstNode);
    }

    @WriteTransactional
    public String saveReturn(BpmnVO vo) {
        if (vo.getFlowData().getInstNodeId() == null || vo.getFlowData().getNodeId() == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        FlowInstNodeBO flowInstNode = flowInstNodeDao.queryBy(vo.getFlowData().getInstNodeId());
        if (flowInstNode == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        FlowNodeBO flowNode = flowNodeDao.queryBy(vo.getFlowData().getNodeId());
        if (flowNode == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        FlowInstBO flowInst = this.flowInstDao.queryBy(flowInstNode.getInstId());
        if (flowInst == null) {
            return ResultUtil.failureResult("参数不正确！");
        }
        if (!FlowConstants.FlowInst.DEAL_STATUS_UNDERWAY.equals(flowInst.getDealStatus())) {
            return ResultUtil.failureResult("流程为非待办中，不允许退回！");
        }

        // 查询退回节点
        FlowInstNodeBO preFlowInstNode = bpmnEngine.findPreInstNode(flowNode.getId(), flowInstNode);
        if (preFlowInstNode == null) {
            return ResultUtil.failureResult("退回节点未办理，不允许退回！");
        }

        // 办结当前节点
        flowInstNode.setDealStatus(FlowConstants.FlowInstNode.DEAL_STATUS_FINISHED);
        flowInstNode.setEndTime(System.currentTimeMillis());
        flowInstNodeDao.update(flowInstNode);

        // 待办办结
        FlowPendingBO pendingQO = new FlowPendingBO();
        pendingQO.setInstId(flowInst.getId());
        pendingQO.setInstNodeId(vo.getFlowData().getInstNodeId());
        pendingQO.setIfRead(Constants.NO);
        pendingQO.setStatus(FlowConstants.FlowPending.STATUS_PENDING);

        this.flowPendingDao.queryList(pendingQO).forEach(pendingBO -> {
            pendingBO.setStatus(FlowConstants.FlowPending.STATUS_FINISHED);
            pendingBO.setFinishedTime(System.currentTimeMillis());
            this.flowPendingDao.update(pendingBO);
        });

        return ResultUtil.successResult("退回成功！", flowInstNode);
    }

    @WriteTransactional
    public void updateCurrMan(String instId) {
        FlowInstBO flowInst = flowInstDao.queryBy(instId);
        List<Map<String, Object>> dataList = this.flowInstNodeDao.queryCurrDealMan(flowInst.getId());
        if (!dataList.isEmpty()) {
            Set<String> currNodeNameSet = new HashSet<>();
            Set<String> currOrgNameSet = new HashSet<>();
            Set<String> currManNameSet = new HashSet<>();
            for (Map<String, Object> data : dataList) {
                currNodeNameSet.add((String) data.get("curr_node_name"));
                currOrgNameSet.add((String) data.get("curr_org_name"));
                currManNameSet.add((String) data.get("curr_man_name"));
            }

            String currNodeNames = String.join(",", currNodeNameSet);
            String currOrgNames = String.join(",", currOrgNameSet);
            String currManNames = String.join(",", currManNameSet);

            if (currNodeNames.length() > 4000) {
                currNodeNames = currNodeNames.substring(0, 4000);
            }
            if (currOrgNames.length() > 4000) {
                currOrgNames = currOrgNames.substring(0, 4000);
            }
            if (currManNames.length() > 4000) {
                currManNames = currManNames.substring(0, 4000);
            }

            flowInst.setCurrNodeNames(currNodeNames);
            flowInst.setCurrOrgNames(currOrgNames);
            flowInst.setCurrManNames(currManNames);
        } else {
            flowInst.setCurrNodeNames(null);
            flowInst.setCurrOrgNames(null);
            flowInst.setCurrManNames(null);
        }

        this.flowInstDao.update(flowInst);
    }

    @WriteTransactional
    public String transfer(BpmnBO bo) {
        if (Tool.isBlank(bo.getReceiveManId())
                || Tool.isBlank(bo.getCurrEmpId())
                || Tool.isBlank(bo.getInstNodeId())) {
            return ResultUtil.failureResult("参数不正确！");
        }

        EmployeeBO employeeBO = this.employeeDao.queryBy(bo.getReceiveManId());
        if (employeeBO == null) {
            return ResultUtil.failureResult("参数不正确！");
        }

        // 修改待办处理人
        FlowPendingBO flowPendingQO = new FlowPendingBO();
        flowPendingQO.setInstNodeId(bo.getInstNodeId());
        flowPendingQO.setCurrManId(bo.getCurrEmpId());
        FlowPendingBO flowPendingBO = flowPendingDao.queryByOne(flowPendingQO);
        if (flowPendingBO == null
                || !bo.getInstNodeId().equals(flowPendingBO.getInstNodeId())
                || !bo.getCurrEmpId().equals(flowPendingBO.getCurrManId())) {
            return ResultUtil.failureResult("非本人待办流程不允许转单！");
        }
        flowPendingBO.setCurrManId(employeeBO.getId());
        flowPendingDao.update(flowPendingBO);



        OrganizationBO organizationQO = new OrganizationBO();
        organizationQO.setEmployeeId(employeeBO.getId());
        List<OrganizationBO> list = this.organizationDao.queryList(organizationQO);
        if (list.isEmpty()) {
            return ResultUtil.failureResult("所选人员未配置部门，请先配置！");
        }
        OrganizationBO organization = list.getFirst();
        FlowInstNodeBO flowInstNodeBO = flowInstNodeDao.queryBy(bo.getInstNodeId());
        flowInstNodeBO.setDealManId(employeeBO.getId());
        flowInstNodeBO.setDealManName(employeeBO.getName());
        flowInstNodeBO.setDealOrgId(organization.getId());
        flowInstNodeBO.setDealOrgName(organization.getName());
        flowInstNodeDao.update(flowInstNodeBO);

        return ResultUtil.successResult("转单成功！");
    }

    public Map<String, Map<String, Object>> queryNodeDataList(BpmnBO bo) {
        if (Tool.isBlank(bo.getInstId())) return null;

        List<FlowInstNodeBO> instNodeList = flowInstNodeDao.queryListByInstId(bo.getInstId());
        Map<String, FlowInstNodeBO> nodeMap = instNodeList.stream()
                .filter(item -> Tool.isNotBlank(item.getDataId()))
                .collect(Collectors.toMap(
                        FlowInstNodeBO::getCurrFlowNodeId,
                        Function.identity(),
                        BinaryOperator.maxBy(Comparator.comparing(FlowInstNodeBO::getCreateTime))));

        Map<String, Map<String, Object>> resultMap = new HashMap<>();
        nodeMap.forEach((key, value) -> {
            FlowNodeBO flowNode = flowNodeDao.queryBy(value.getCurrFlowNodeId());
            if (flowNode == null || Tool.isBlank(flowNode.getModelId())) return;

            resultMap.put(flowNode.getId(), codelessService.queryBy(flowNode.getModelId(), value.getDataId()));

        });
        return resultMap;
    }
}
